﻿using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;

namespace Daybreak.Core.Rendering
{
    public class Mesh
    {
        public class DataSlot
        {
            internal int indexCount;
            internal int vertexCount;
            private int _vertexBufferSize;
            //private int _currentMeshIndex;
            private List<Mesh> _meshList = new List<Mesh>();

            //internal bool needsInvalidate;

            /// <summary>
            /// Buffer for Shared vertex data
            /// </summary>
            public VertexData SharedVertexData;
            public IndexBuffer SharedIndexBuffer;

            public DataSlot() { }

            public void AddMesh(Mesh mesh, bool check)
            {
                if (check)
                {
                    if (_meshList.Contains(mesh))
                        return;
                }

                for (int i = 0; i < mesh.PartList.Count; i++)
                {
                    Drawable.Topology topology = mesh.PartList[i].Topology;
                    int vCount = topology.VertexIndexCount;

                    vertexCount += vCount;
                    indexCount += vCount;
                    _vertexBufferSize += vCount * topology.FloatsPerVertex;

                }

                _meshList.Add(mesh);

            }

            public void AddMeshPart(Mesh.Part part)
            {
                int vCount = part.Topology.VertexIndexCount;

                vertexCount += vCount;
                indexCount += vCount;
                _vertexBufferSize += vCount * part.Topology.FloatsPerVertex;

            }


            public bool EnsureVertexData(Mesh firstMesh, Drawable.Container container, bool useTangents)
            {
                if (SharedVertexData != null)
                    return true;

                if (_meshList.Count == 0)
                    return false;

                Scene.Controller.Instance skin = container as Scene.Controller.Instance;

                int perVertexBoneCount = 0;

                if (skin != null)
                {
                    perVertexBoneCount = skin.Skin.Assignments.Length;

                    // allocate additional space for vertex bone weights
                    _vertexBufferSize += (vertexCount * skin.Skin.AdditionalVertexElements);
                }

                // building shared vertex data ...

                float[] rawVertexData = new float[_vertexBufferSize];
                int[] rawIndexData = new int[indexCount];

                // TODO: find the better way...
                VertexElement[] vertexElements = firstMesh.PartList[0].Topology.GetVertexElements(perVertexBoneCount);

                SharedVertexData = VertexData.Create(vertexCount, vertexElements, BufferUsage.None);
                SharedIndexBuffer = new IndexBuffer(Engine.GraphicsDevice, indexCount*4, BufferUsage.None, IndexElementSize.ThirtyTwoBits);

                unsafe
                {
                    fixed (float* vertexDataAddr = &rawVertexData[0])
                    {
                        fixed (int* indexDataAddr = &rawIndexData[0])
                        {
                            GeometryAssemblerArgs args = new GeometryAssemblerArgs();

                            args.VertexDataPtr = vertexDataAddr;
                            args.IndexDataPtr = indexDataAddr;
                            args.SharedIndexBuffer = SharedIndexBuffer;
                            args.SharedVertexData = SharedVertexData;

                            for (int i = 0; i < _meshList.Count; i++)
                            {
                                _meshList[i].BuildGeometryData(ref args, container);
                            }
                        }
                    }
                }

                SharedIndexBuffer.SetData<int>(rawIndexData);

                SharedVertexData.CommonVertexBuffer.SetData<float>(rawVertexData);

                return true;

            }

        }

        public class VertexStream
        {
            public BoundingBox BoundingVolume;

            VertexArray[] _vertexArrays;
            internal VertexData vertexData;

            public VertexStream()
            {
                _vertexArrays = new VertexArray[16];
            }

            public void SetArrays(List<VertexPositionNormalTexture> dataList)
            {
                Vector3[] pArr = new Vector3[dataList.Count];
                Vector3[] nArr = new Vector3[dataList.Count];
                Vector2[] tArr = new Vector2[dataList.Count];

                VertexPositionNormalTexture vertex;

                for (int i = 0; i < dataList.Count; i++)
                {
                    vertex = dataList[i];

                    pArr[i] = vertex.Position;
                    nArr[i] = vertex.Normal;
                    tArr[i] = vertex.TextureCoordinate;
                }

                SetArray(pArr, VertexElementUsage.Position, 0);
                SetArray(nArr, VertexElementUsage.Normal, 0);
                SetArray(tArr, VertexElementUsage.TextureCoordinate, 0);
            }

            public void SetArrays(VertexPositionNormalTexture[] dataArr)
            {
                Vector3[] pArr = new Vector3[dataArr.Length];
                Vector3[] nArr = new Vector3[dataArr.Length];
                Vector2[] tArr = new Vector2[dataArr.Length];

                for (int i = 0; i < dataArr.Length; i++)
                {
                    pArr[i] = dataArr[i].Position;
                    nArr[i] = dataArr[i].Normal;
                    tArr[i] = dataArr[i].TextureCoordinate;
                }

                SetArray(pArr, VertexElementUsage.Position, 0);
                SetArray(nArr, VertexElementUsage.Normal, 0);
                SetArray(tArr, VertexElementUsage.TextureCoordinate, 0);
            }

            public void SetArray(Color[] dataArr, VertexElementUsage usage, int set)
            {
                int index = (int)usage;

                if (_vertexArrays[index] == null)
                    _vertexArrays[index] = new VertexArray.Colors(dataArr, usage, set);
                else
                    _vertexArrays[index].SetItems(dataArr, set);

            }

            public void SetArray(Vector2[] dataArr, VertexElementUsage usage, int set)
            {
                int index = (int)usage;

                if (_vertexArrays[index] == null)
                    _vertexArrays[index] = new VertexArray.Float2(dataArr, usage, set);
                else
                    _vertexArrays[index].SetItems(dataArr, set);

            }

            public void SetArray(Vector3[] dataArr, VertexElementUsage usage, int set)
            {
                int index = (int)usage;

                if (_vertexArrays[index] == null)
                    _vertexArrays[index] = new VertexArray.Float3(dataArr, usage, set);
                else
                    _vertexArrays[index].SetItems(dataArr, set);

                if (usage != VertexElementUsage.Position)
                    return;

                BoundingVolume = BoundingBox.CreateFromPoints(dataArr);
            }

            public void SetArray(Vector4[] dataArr, VertexElementUsage usage, int set)
            {
                int index = (int)usage;

                if (_vertexArrays[index] == null)
                    _vertexArrays[index] = new VertexArray.Float4(dataArr, usage, set);
                else
                    _vertexArrays[index].SetItems(dataArr, set);

            }

            public VertexArray GetChannel(VertexElementUsage usage)
            {
                int index = (int)usage;

                if (_vertexArrays[index] == null)
                    return null; // TODO calculate on the fly...

                return _vertexArrays[index];

            }

            public T[] GetArray<T>(VertexElementUsage usage, int set) where T : struct, IEquatable<T>
            {
                int index = (int)usage;

                if (_vertexArrays[index] == null)
                    return null;

                return _vertexArrays[index].GetItems(set) as T[];

            }

            ///// <summary>
            ///// assemble primitives depending on topology
            ///// </summary>
            //internal void AssembleVertexData(Mesh mesh)
            //{
            //    int vertexCounter = 0;
            //    int indexCount = mesh.IndexCount;

            //    float[] rawVertexData = new float[mesh.vertexBufferSize];
            //    int[] rawIndexData = new int[indexCount];

            //    IndexBuffer indexBuffer = new IndexBuffer(Engine.GraphicsDevice, indexCount*4, BufferUsage.None, IndexElementSize.ThirtyTwoBits);

            //    unsafe
            //    {
            //        fixed (float* vertexDataAddr = &rawVertexData[0])
            //        {
            //            fixed (int* indexDataAddr = &rawIndexData[0])
            //            {
            //                float* vertexDataPtr = vertexDataAddr;
            //                int* indexDataPtr = indexDataAddr;

            //                mesh.BuildGeometryData(ref vertexDataPtr, ref indexDataPtr, ref vertexCounter, indexBuffer);
            //            }
            //        }
            //    }

            //    indexBuffer.SetData<int>(rawIndexData);

            //    // TODO: find the better way...
            //    VertexElement[] vertexElements = mesh.PartList[0].Topology.GetVertexElements(0);

            //    vertexData = VertexData.Create(mesh.vertexCount, vertexElements, BufferUsage.None);

            //    vertexData.VertexBuffer0.SetData<float>(rawVertexData);

            //}

            //public VertexData GetVertexData(Mesh mesh, Drawable.Container container)
            //{
            //    if (vertexData == null)
            //    {
            //        //_vertexData = new VertexData();

            //        AssembleVertexData(mesh);
            //    }

            //    return vertexData;
            //}

            public void SetUpVertexInput(ref Drawable.Topology.InputOffset input, VertexElementUsage usage, int offset, int set)
            {
                VertexArray arr = _vertexArrays[(int)usage];

                if (arr == null)
                    return;

                arr.SetUpVertexInput(ref input, offset, set);
            }

            public int ArraysCount
            {
                get
                {
                    int result = 0;

                    for (int i = 0; i < _vertexArrays.Length; i++)
                        if (_vertexArrays[i] != null)
                            result += _vertexArrays[i].ItemSetsCount;

                    return result;
                }
            }

            public VertexArray[] Arrays
            {
                get { return _vertexArrays; }
            }


        }

        public class Part
        {
            public class Instance : Drawable
            {
                Part _meshPart;

                public Instance(Part part, IMaterial material)
                {
                    _meshPart = part;

                    if (material == null)
                        _material = part.Appearance;
                    else
                        _material = material;
                }

                public override void GetGeometryData(RenderItem slot, Drawable.Container container)
                {
                    bool useTangents = Material.Effect.UseTangents;

                    VertexData vd = _meshPart.GetVertexData(container, useTangents);
                    IndexData id = _meshPart.IndexData;

                    slot.VertexData = vd;
                    slot.IndexBuffer = id.IndexBuffer;
                    slot.Material = Material;
                    slot.Opacity = 1;
                    slot.PrimitiveCount = _meshPart.Topology.PrimitiveCount;
                    slot.PrimitiveType = _meshPart.Topology.Type;
                    slot.StartIndex = id.IndexStart;

                }


                public override string ToString()
                {
                    return _meshPart.ToString();
                }


                public override int PrimitiveCount
                {
                    get { return _meshPart.Topology.PrimitiveCount; }
                }

            }

            private Drawable.Topology _topology;
            private Mesh _owner;

            /// <summary>
            /// Default material
            /// </summary>
            public IMaterial Appearance;

            protected IndexData _indexData;

            public Part(Mesh owner, Drawable.Topology topology)
            {
                _owner = owner;
                _topology = topology;
            }

            internal unsafe void BuildGeometryData(ref GeometryAssemblerArgs args, Drawable.Container container)
            {
                if (_topology == null)
                    return;

                _topology.BuildVertexData(ref args.VertexDataPtr, _owner.Vertices, container);

                int idxCount = _topology.BuildIndexData(ref args.IndexDataPtr, args.VertexCounter);

                if (_indexData == null)
                    _indexData = new IndexData();

                _indexData.IndexCount = idxCount;
                _indexData.IndexStart = args.VertexCounter;
                _indexData.IndexBuffer = args.SharedIndexBuffer;

                args.VertexCounter += idxCount;
            }

            //internal unsafe void BuildGeometryData(ref float* rawVertexData, ref int* rawIndexData, ref int vertexCounter)
            //{
            //    if (_topology == null)
            //        return;

            //    _topology.BuildVertexData(ref rawVertexData, _owner.Vertices.Arrays, null);

            //    int idxCount = _topology.BuildIndexData(ref rawIndexData, vertexCounter);

            //    if (_indexData == null)
            //        _indexData = new IndexData();

            //    _indexData.IndexCount = idxCount;
            //    _indexData.IndexStart = vertexCounter;

            //    vertexCounter += idxCount;
            //}

            public override string ToString()
            {
                return string.Format("<{0} count='{1}' material='{2}'>", _topology.GetType().Name, _topology.PrimitiveCount, Appearance.Name);
            }

            public Drawable.Topology Topology
            {
                get { return _topology; }
            }

            public VertexData GetVertexData(Drawable.Container container, bool useTangents)
            {
                return _owner.GetVertexData(container, useTangents);
            }

            public IndexData IndexData
            {
                get { return _indexData; }
            }

        }

        public List<Part> PartList = new List<Part>();
        public VertexStream Vertices;

        DataSlot slot;
        int vertexBufferSize;
        int vertexCount;

        public Mesh()
        {
            Vertices = new VertexStream();
        }

        public Mesh(DataSlot slot)
        {
            Vertices = new VertexStream();

            SetGeometrySlot(slot);
        }


        public void AddMeshPart(Drawable.Topology topology, IMaterial mat)
        {
            if (mat == null)
                return;

            vertexCount += topology.VertexIndexCount;
            vertexBufferSize += topology.VertexIndexCount * topology.FloatsPerVertex;

            Part part = new Part(this, topology);
            part.Appearance = mat;
            PartList.Add(part);

            if (slot != null)
                slot.AddMeshPart(part);

        }

        public void CorrectAxis(IAxisCorrector corrector, bool convertUnits)
        {
            if (corrector == null)
                return;

            Vector3[] vertexArr = Vertices.GetArray<Vector3>(VertexElementUsage.Position, 0);

            if (vertexArr == null)
                return;

            for (int i = 0; i < PartList.Count; i++)
                if (PartList[i] != null && PartList[i].Topology != null)
                    PartList[i].Topology.CorrectAxis(corrector);

            for (int i = 0; i < vertexArr.Length; i++)
                corrector.CorrectPosition(ref vertexArr[i]);

            if (convertUnits)
            {
                for (int i = 0; i < vertexArr.Length; i++)
                    corrector.MeasureVector(ref vertexArr[i]);

                corrector.MeasureVector(ref Vertices.BoundingVolume.Max);
                corrector.MeasureVector(ref Vertices.BoundingVolume.Min);
                //Vertices.BoundingVolume.Radius *= corrector.TargetScale;

            }

            vertexArr = Vertices.GetArray<Vector3>(VertexElementUsage.Normal, 0);

            if (vertexArr == null)
                return;

            for (int i = 0; i < vertexArr.Length; i++)
                corrector.CorrectPosition(ref vertexArr[i]);

        }

        public bool SetGeometrySlot(DataSlot existingSlot)
        {
            slot = existingSlot;

            if (existingSlot != null)
                existingSlot.AddMesh(this, false);

            //  force vertex data rebuilding
            Vertices.vertexData = null;

            return true;
        }

        public DataSlot RequestGeometrySlot()
        {
            if (slot != null)
                return slot;

            slot = new DataSlot();
            slot.AddMesh(this, false);

            //  force vertex data rebuilding
            Vertices.vertexData = null;

            return slot;
        }

        internal void BuildGeometryData(ref GeometryAssemblerArgs args, Drawable.Container container)
        {
            for (int i = 0; i < PartList.Count; i++)
            {
                PartList[i].BuildGeometryData(ref args, container);
            }

            Vertices.vertexData = args.SharedVertexData;
        }

        //internal unsafe void BuildGeometryData(ref float* vertexDataPtr, ref int* indexDataPtr, ref int vertexCounter, IndexBuffer indexBuffer)
        //{
        //    for (int i = 0; i < PartList.Count; i++)
        //    {
        //        PartList[i].BuildGeometryData(ref vertexDataPtr, ref indexDataPtr, ref vertexCounter);

        //        // assign shared index buffer
        //        PartList[i].IndexData.IndexBuffer = indexBuffer;

        //    }

        //}

        public override string ToString()
        {
            return string.Format("<mesh> parts:{0}", PartList.Count);
        }

        public VertexData GetVertexData(Drawable.Container container, bool useTangents)
        {
            if (slot == null)
                RequestGeometrySlot().EnsureVertexData(this, container, useTangents);
            else
                slot.EnsureVertexData(this, container, useTangents);

            return Vertices.vertexData;
        }

        public int IndexCount
        {
            get
            {
                int indexCount = 0;

                for (int i = 0; i < PartList.Count; i++)
                    indexCount += PartList[i].Topology.VertexIndexCount;

                return indexCount;
            }
        }

    }

    public unsafe struct GeometryAssemblerArgs
    {
        public int VertexCounter;
        public float* VertexDataPtr;
        public int* IndexDataPtr;

        public VertexData SharedVertexData;
        public IndexBuffer SharedIndexBuffer;


    }
}
